using System;
using System.Collections.Generic;
using System.ComponentModel;

using System.Drawing;
using System.Text;
using System.Windows.Forms;
using CodePlex.SpaceBlock;
using CodePlex.SpaceBlock.FS;
using System.Threading;
using CodePlex.Resourceful;
using CodePlex.Resourceful.Amazon.S3;
using System.Net;

namespace CodePlex.SpaceBlock.UI.Controls
{
    public partial class TransferForm : CodePlex.SpaceBlock.UI.Controls.BaseForm
    {
        private static class Actions
        {
            public static readonly string ClearSelection = "ClearSelection";
            public static readonly string CannedACL = "CannedACL";
            public static readonly string ContentType = "ContentType";
            public static readonly string UserMetadata = "UserMetadata";
        }

        public event EventHandler TransferCompleted;

        public TransferForm()
        {
            InitializeComponent();
        }

        public void Initialize(IFileSystem from, List<Path> fromPaths, IFileSystem to, Path toFolder)
        {
            this.Icon = UIManager.Instance.GetIcon("Transfer");


            this.Status("Initializing transfer...");
            this.Title("Initializing transfer...");

            this.Speed("");
            this.Amount("");


            Thread transferThread = null;

            _cancelButton.Click += delegate
            {
                if (transferThread != null)
                {
                    transferThread.Abort();
                }
                this.Dispose();
            };


            _transferButton.Enabled = false;


             List<IFileItem> fromFiles = null;
             List<Path> fromFolders = null;


             _fromBox.Text = from.Caption;
             _toBox.Text = toFolder.ToString();

            DataGridView2 grid = new DataGridView2();
            bool showMetadata = (to is S3FileSystem);


            ActionManager actions = new ActionManager(this, null, _contextMenu, null);

            // set up context menu
            if (showMetadata)
            {
                
                grid.ContextMenuStrip = _contextMenu;

                actions.NewAction(Actions.ContentType, "Set Content-Type", 0, Keys.None, delegate
                {

                });

                foreach (string contentTypeCategory in SpaceBlockUtils.GetContentTypeCategories())
                {

                    string categoryName = Actions.ContentType + "." + contentTypeCategory;

                    actions.NewSubAction(Actions.ContentType, categoryName, contentTypeCategory, 0, Keys.None, delegate
                    {
                       
                    });

                    foreach (string contentType in SpaceBlockUtils.GetContentTypes(contentTypeCategory))
                    {
                        string localContentType = contentType;
                        string name = categoryName + "." + contentType;

                        actions.NewSubAction(categoryName, name, contentType, 0, Keys.None, delegate
                        {

                               actions.SetChecked(Actions.ContentType, false);
                               actions.SetChecked(name, true);

                               foreach (DataGridViewRow row in grid.SelectedRows)
                               {
                                   if (row.Tag is IFileItem)
                                   {
                                       row.Cells[2].Value = localContentType;
                                   }
                               }

                         });
                    }
                    
                }


                actions.NewAction(Actions.CannedACL, "Set Canned ACL", 0, Keys.None, delegate
                {
                
                });

                foreach (string cannedACL in S3Constants.CannedAccessPolicies.All)
                {

                    string localCannedACL = cannedACL;
                    string name = Actions.CannedACL + "." + cannedACL;

                    actions.NewSubAction(Actions.CannedACL, name, cannedACL, 0, Keys.None, delegate
                    {
                       actions.SetChecked(Actions.CannedACL, false);
                       actions.SetChecked(name, true);

                       foreach (DataGridViewRow row in grid.SelectedRows)
                       {
                           if (row.Tag is IFileItem)
                           {
                               row.Cells[3].Value = localCannedACL;
                           }
                       }
                    });



                }

                UserMetadataForm form = new UserMetadataForm();
                form.Icon = this.Icon;

                actions.NewAction(Actions.UserMetadata, "Edit User Metadata...", 0, Keys.None, delegate
                {
                    form.Metadata = grid.SelectedRows[0].Cells[4].Value as Metadata;

                    if (form.ShowDialog(this) == DialogResult.OK)
                    {
                        foreach (DataGridViewRow row in grid.SelectedRows)
                        {
                            if (row.Tag is IFileItem)
                            {
                                row.Cells[4].Value = form.Metadata;
                            }
                        }
                    }

                });

                actions.NewAction(Actions.ClearSelection, "Clear Selection", 0, Keys.None, delegate
                {
                    grid.ClearSelection();
                });

                grid.SelectionChanged += delegate
                {
                    actions.SetChecked(Actions.ContentType, false);
                    actions.SetChecked(Actions.CannedACL, false);
                    actions.SetChecked(Actions.UserMetadata, false);

                    bool enabled = grid.SelectedRows.Count > 0;

                    foreach (DataGridViewRow row in grid.SelectedRows)
                    {
                        if (row.Tag is Path)
                        {
                            enabled = false;
                        }
                    }
                };

            }

            this.InBackground(delegate
            {

                fromFiles = new List<IFileItem>();
                fromFolders = new List<Path>();

                Action<Path> add = null;
                
                add = delegate(Path folderPath)
                {
                    fromFolders.Add(folderPath);

                    foreach (IPathItem item in from.GetItems(folderPath))
                    {
                        if (item.Path.IsFolder)
                        {
                            add(item.Path);
                        }
                        else
                        {
                            fromFiles.Add(item as IFileItem);
                        }
                    }
                };


                Path basePath = null;
                foreach(Path fromPath in fromPaths) {

                    basePath = basePath ?? fromPath.Parent;

                    if (fromPath.IsFolder)
                    {
                        add(fromPath);
                    }
                    else
                    {
                        fromFiles.Add(from.GetItem(fromPath) as IFileItem);
                    }
                }

                this.InDisplay(delegate
                {
                    
                    DataGridViewColumn col = grid.AddColumnText("", 300);
                    col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;

                    if (showMetadata)
                    {
                        grid.AddColumnText("Content Type", 150);
                        grid.AddColumnText("Canned ACL", 150);
                        grid.AddColumnText("User Metadata", 150);
                    }
                   
                    foreach (Path fromFolder in fromFolders)
                    {
                        string to2 = fromFolder.ToString().Substring(basePath.ToString().Length);

                        List<object> values = new List<object>();
                        values.Add(UIManager.Instance.GetSmallImage("Folder"));
                        values.Add(to2);
                        if (showMetadata)
                        {
                            values.Add("");
                            values.Add("");
                            values.Add("");
                        }
                        
                        grid.AddRow(fromFolder, values.ToArray());
                    }

                    foreach (IFileItem fromFile in fromFiles)
                    {
                        string to2 = fromFile.Path.ToString().Substring(basePath.ToString().Length);

                        List<object> values = new List<object>();
                        values.Add(UIManager.Instance.GetSmallImage("File"));
                        values.Add(to2);
                        if (showMetadata)
                        {
                            values.Add(MimeMap.Get(fromFile.Path.ToString()));
                            values.Add(S3Constants.CannedAccessPolicies.Private);
                            values.Add(new Metadata());
                        }

                        
                        grid.AddRow(fromFile, values.ToArray());
                    }

                    grid.Sort(grid.Columns[1], ListSortDirection.Ascending);
                    UIUtils.FillWith(_itemsHolder, grid);

                    this.Status("Ready.");
                    
                    this.Title("Transfer ready to {0}.", to.Caption);

                    _transferButton.Enabled = true;

                });


            });


            bool done = false;

            _transferButton.Click += delegate
            {
                if (done)
                {
                    this.Dispose();
                    return;
                }

                _transferButton.Enabled = false;
                _overallProgressBar.Value = 0;


                actions.SetEnabled(Actions.CannedACL, false);
                actions.SetEnabled(Actions.ContentType, false);
                actions.SetEnabled(Actions.UserMetadata, false);
                actions.SetEnabled(Actions.ClearSelection, false);
             
                transferThread = this.InBackground(delegate
                {

                    long bytesTotal = 0L;
                    long bytesTransferred = 0L;


                    Utils.ForEach(fromFiles, delegate(IFileItem item)
                    {
                        bytesTotal += item.Size;
                    });


                    DateTime timeStart = DateTime.Now;

                    Action<long> updateProgress = delegate(long current)
                    {
                        this.InDisplay(delegate
                        {

                          string prefix = "Transferring...";
                          if (done)
                          {
                              prefix = "Transfer complete.";
                          }
                         

                          long accurate = bytesTransferred + current;

                          double pct = 0D;

                          if (bytesTotal > 0)
                          {
                              pct = (double)accurate / (double)bytesTotal;
                          }

                          _overallProgressBar.Value = (int)(pct * (double)_overallProgressBar.Maximum);

                          TimeSpan elapsed = DateTime.Now - timeStart;

                          double bps = (double)(accurate) / elapsed.TotalSeconds;

                          if (bps < 0)
                          {
                              bps = 0;
                          }

                          this.Status(prefix);
                          this.Speed("{0}/s", Utils.ComputeSizeString((long)bps));
                          this.Amount("{0}/{1}",
                              Utils.ComputeSizeString(accurate),
                              Utils.ComputeSizeString(bytesTotal));

                          if (done)
                          {
                              pct = 1;
                              _overallProgressBar.Value = _overallProgressBar.Maximum;
                          }
                         
                          this.Title("{0:0%} complete to {1}.", pct, to.Caption);
                          
                      });
                    };


                    foreach (DataGridViewRow row in grid.Rows)
                    {

                        string part = row.Cells[1].Value as string;

                        Path toPath = toFolder.Combine(part);

                        // upload folder
                        Path fromFolder = row.Tag as Path;
                        if (fromFolder != null)
                        {
                            to.EnsureFolderExists(toPath);
                            continue;
                        }


                        // upload file
                        IFileItem fromFile = row.Tag as IFileItem;
                        if (fromFile == null)
                        {
                            continue;
                        }

                        NameValuePairs metadata = new NameValuePairs();
                        if (showMetadata)
                        {
                            // x -> s3
                            metadata[HttpHeader.ContentType] =  row.Cells[2].Value as string;
                            metadata[S3Constants.Headers.CannedAccessPolicy] = row.Cells[3].Value as string;

                            Metadata user = row.Cells[4].Value as Metadata;
                            if (user != null)
                            {
                                foreach (string key in user.Keys)
                                {
                                    metadata[S3Constants.Headers.AmazonUserMetadataPrefix + key] = user[key];
                                }
                            }

                            // md5 (pre)
                            this.InDisplay(delegate
                            {
                                this.Status("Calculating hash...");
                            });
                            metadata[HttpHeader.ContentMD5] = Utils.ToBase64(fromFile.QuickMD5);

                        }

                        try
                        {
                            to.Transfer(fromFile, toPath, metadata, updateProgress);
                        }
                        catch (WebException ex)
                        {
                            // hide exception box if user cancelled...
                            if (ex.Message.Contains("The request was canceled"))
                            {
                                return;
                            }
                            throw;
                        }
                        bytesTransferred += fromFile.Size;
                      
                    }


                    transferThread = null;
                    this.InDisplay(delegate
                    {
                        done = true;
                        updateProgress(0);
                        _transferButton.Text = "Close";
                        _transferButton.Enabled = true;
                        _cancelButton.Enabled = false;
                        OnTransferCompleted();

                    });

                });

            };


        }








        private void Title(string message, params object[] parameters)
        {
            this.Text = string.Format(message, parameters);
        }

        private void Status(string message, params object[] parameters)
        {
            _statusBox.Text = string.Format(message, parameters);
        }

        private void Speed(string message, params object[] parameters)
        {
            _speedBox.Text = string.Format(message, parameters);
        }

        private void Amount(string message, params object[] parameters)
        {
            _amountBox.Text = string.Format(message, parameters);
        }

        protected void OnTransferCompleted()
        {
            if (this.TransferCompleted != null)
            {
                this.TransferCompleted(this, EventArgs.Empty);
            }
        }


    }
}

